package com.mebs.edi.scheduler.jobs;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.SocketException;
import java.util.List;
import java.util.Properties;

import javax.mail.MessagingException;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.w3c.dom.Document;
import org.xml.sax.SAXParseException;

import com.mebs.edi.datalayer.DaoFactory;
import com.mebs.edi.datalayer.GenericDao;
import com.mebs.edi.model.edi.EdiIsa;
import com.mebs.edi.model.email.Email;
import com.mebs.edi.parser.EdiToMebsMapper;
import com.mebs.edi.parser.dom.DomToModelConverter;
import com.mebs.edi.parser.xml.XmlParser;
import com.mebs.edi.parser.xml.XmlValidationException;
import com.mebs.edi.scheduler.InitApplication;
import com.mebs.edi.scheduler.email.EmailSender;
import com.mebs.edi.scheduler.ftp.FtpAccessor;

/**
 * 
 * This job is responsible for processing 211 xml files, stored on FTP
 * 
 * @author nit
 *
 */
public class ProcessXmlJob implements Job {
	
	private static final String PROPERTY_FTP_SERVER = "ftp.server";
	private static final String PROPERTY_FTP_USERNAME = "ftp.username";
	private static final String PROPERTY_FTP_PASSWORD = "ftp.password";
	
	private static final String PROPERTY_FTP_NEW_211_XML_DIR = "ftp.new211XmlDir";
	private static final String PROPERTY_FTP_ALL_211_XML_DIR = "ftp.all211XmlDir";
	private static final String PROPERTY_FTP_ERROR_211_XML_DIR = "ftp.error211XmlDir";
	
	private static final String PROPERTY_211_DOWNLOAD_DIR = "processXml.211DownloadDir";
	
	private static final String PROPERTY_SCHEMA_211_LOCATION = "schema.211location";
	
	private static final String PROPERTY_SMTP_HOST = "email.smtp.host";
	private static final String PROPERTY_SMTP_PORT = "email.smtp.port";
	
	private static final String PROPERTY_211_NOTIFICATION_RECIPIENTS = "email.211notificationRecipients";
	private static final String PROPERTY_NOTIFICATION_SENDER = "email.notificationSender";
	
	private static final Logger LOG = Logger.getLogger(ProcessXmlJob.class);
	
	/**
	 * the body of this method is executed by the Quartz scheduler
	 * 
	 * @param jeContext - Job Execution Context
	 * @throws JobExecutionException
	 */
	public void execute(JobExecutionContext jeContext) throws JobExecutionException {
		LOG.debug("executing xml processing job");
		
		JobDataMap jobData = jeContext.getMergedJobDataMap();
		
		String server = jobData.getString(PROPERTY_FTP_SERVER);
		String username = jobData.getString(PROPERTY_FTP_USERNAME);
		String password = jobData.getString(PROPERTY_FTP_PASSWORD);
		
		String new211Folder = jobData.getString(ProcessXmlJob.PROPERTY_FTP_NEW_211_XML_DIR);
		String all211Folder = jobData.getString(ProcessXmlJob.PROPERTY_FTP_ALL_211_XML_DIR);
		String downloadFolderName = jobData.getString(ProcessXmlJob.PROPERTY_211_DOWNLOAD_DIR);
		
		FtpAccessor ftpAccessor = new FtpAccessor(server, username, password);
		
		try {
			String xsd211name = jobData.getString(ProcessXmlJob.PROPERTY_SCHEMA_211_LOCATION);
			File xsd211 = new File(xsd211name);
			XmlParser parser = new XmlParser(xsd211);
			
			DomToModelConverter converter = new DomToModelConverter();
			EdiToMebsMapper mapper = new EdiToMebsMapper();
			GenericDao genericDao = DaoFactory.getInstance().getGenericDao();
			
			// download files from FTP
			List<File> downloadedFiles = ftpAccessor.downloadFiles(new211Folder, downloadFolderName, true);
			
			// process downloaded files
			for (File f : downloadedFiles) {
				LOG.info("processing: " + f.getName());
				try {
					// parse XML
					Document dom = parser.parse(f);
					// convert DOM to object
					EdiIsa isa = (EdiIsa)converter.convert(dom.getDocumentElement());
					// populate associated MEBS objects
					mapper.populateMebsClasses(isa);
					// save conversion result to DB
					genericDao.save(isa);
					// upload processed file to archive directory
					ftpAccessor.uploadFile(f, all211Folder);
					//TODO: consider deleting the files from server after uploading them to FTP
				} catch (Exception e) {
					// adding file to exception
					sendErrorNotificationEmail(new ProcessXmlFileException("Unable to process 211 XML", e, f));
					
					String error211folder = jobData.getString(PROPERTY_FTP_ERROR_211_XML_DIR);
					try {
						ftpAccessor.uploadFile(f, error211folder);
					} catch (IOException ioe) {
						LOG.fatal("Cannot upload invalid file " + f.getName() + " to FTP " + error211folder, ioe);
					}
				}
				LOG.info("finished processing: " + f.getName());
			}
			
			LOG.debug("finished xml processing job");
			
		} catch (Exception e) {
			LOG.error("211 XML processing job failed", e);
			sendErrorNotificationEmail(e);
		}
	}
	
	private void sendErrorNotificationEmail(Exception e) {
		Properties applicationProps = InitApplication.getApplicationProperties();
		
		String sender = applicationProps.getProperty(PROPERTY_NOTIFICATION_SENDER);
		String[] recipients = applicationProps.getProperty(PROPERTY_211_NOTIFICATION_RECIPIENTS).split(",");
		String text = constructErrorMessage(e);
		
		Email errorNotification = new Email();
		errorNotification.setSender(sender);
		errorNotification.setRecipients(recipients);
		errorNotification.setSubject("MEBS EDI integration service: processing 211 XML failed");
		errorNotification.setText(text);
		
		String smtpHost = applicationProps.getProperty(PROPERTY_SMTP_HOST);
		String smtpPort = applicationProps.getProperty(PROPERTY_SMTP_PORT);
		
		EmailSender emailSender = new EmailSender(smtpHost, smtpPort);
		try {
			emailSender.sendEmail(errorNotification);
		} catch (MessagingException me) {
			LOG.fatal("Cannot send error notification!", me);
		}
	}
	
	private String constructErrorMessage(Exception e) {
		StringBuilder text = new StringBuilder("211 XML processing job encountered an error:\n\n");
		
		if (e instanceof ProcessXmlFileException) {
			// one of the files could not be processed
			ProcessXmlFileException pe = (ProcessXmlFileException)e;
			text.append("one of the files could not be processed: ");
			text.append(pe.getXml().getName());
			
			Throwable cause = e.getCause();
			if (cause instanceof XmlValidationException) {
				text.append("\n\nvalidating the file against 211.xsd failed:\n");
				
				XmlValidationException xe = (XmlValidationException)cause;
				List<SAXParseException> validationErrors = xe.getValidationErrors();
				for (SAXParseException error : validationErrors) {
					text.append(error.getMessage());
					text.append("\n");
				}
			} else {
				text.append("\n");
				text.append(cause.getClass());
				text.append(": ");
				text.append(cause.getMessage());
			}
			
		} else {
			// something other, perhaps FTP is unavailable
			text.append(e.getClass());
			text.append(": ");
			text.append(e.getMessage());
		}
		
		return text.toString();
	}

}